/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using Gtk;
using System;
using System.Collections.Generic;

using Anculus.Core;
using Anculus.Gui;

using Galaxium.Core;
using Galaxium.Client;
using Galaxium.Protocol;
using Galaxium.Protocol.Gui;
using Galaxium.Protocol.Irc;

namespace Galaxium.Protocol.Irc.GtkGui
{
	public class IrcCommandInputHandler : ICommandInputHandler
	{
		private const string defaultError = "Invalid number of parameters, please use '/help {0}' for more information.";
		
		public IEnumerable<InputCommand> SupportedCommands
		{
			get
			{
				// Official IRC commands that we should have available for normal users.
				yield return new InputCommand (this, "admin", "Instructs the server to return information about the administrator of the server specified by [server], or the current server if [server] is omitted.", "/admin [server]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "away", "Provides the server with a message to automatically send in reply to a PRIVMSG directed at the user, but not to a channel they are on. If [message] is omitted, the away status is removed.", "/away [message]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "commands", "The following commands are supported:\nadmin, away, commands, help, info, invite, join, kick, list, lusers, mode, motd, msg, names, nick, notice, oper, part, quit, servlist, squery, stats, time, topic, userhost, users, version, wallops, who, whois, whowas", "/<command> [parameters]", 99, IrcProtocol.Instance);
				yield return new InputCommand (this, "help", "Allows you to obtain help descriptions and usage information for all supported commands. Optional parameters are shown with [] and required parameters are shown with <>.\n\nFor a list of available commands, type: /commands", "/help <command>", 1, IrcProtocol.Instance);
				yield return new InputCommand (this, "info", "Returns information about the [server], or the current server if [server] is omitted. Information returned includes the server's version, when it was compiled, the patchlevel, when it was started, and any other information which may be considered to be relevant.", "/info [server]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "invite", "Invites <nickname> to the channel <channel>. <channel> does not have to exist, but if it does, only members of the channel are allowed to invite other clients. If the channel mode i is set, only channel operators may invite other clients.", "/invite <nickname> <channel>", 2, IrcProtocol.Instance);
				yield return new InputCommand (this, "join", "Makes the client join the channels in the comma-separated list <channels>, specifying the passwords, if needed, in the comma-separated list [keys]. If the channel(s) do not exist then they will be created.", "/join <channels> [keys]", 1, IrcProtocol.Instance);
				yield return new InputCommand (this, "kick", "Forcibly removes <client> from <channel>. Optionally a [message] can be provided. This command may only be issued by channel operators.", "/kick <channel> <nickname> [message]", 1, IrcProtocol.Instance);
				yield return new InputCommand (this, "list", "Lists all channels on the server. If the comma-separated list <channels> is given, it will return the channel topics. If <server> is given, the command will be forwarded to <server> for evaluation.", "/list [channels] [server]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "lusers", "Returns statistics about the size of the network. If called with no arguments, the statistics will reflect the entire network. If [mask] is given, it will return only statistics reflecting the masked subset of the network. If [server] is given, the command will be forwarded to [server] for evaluation.", "/lusers [mask] [server]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "mode", "Used to set both user and channel modes.", "/mode <nickname> <flags>\n/mode <channel> <flags> [args]", 2, IrcProtocol.Instance);
				yield return new InputCommand (this, "motd", "Returns the message of the day on <server> or the current server if it is omitted.", "/motd [server]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "msg", "Sends <message> to <target>, which is usually a user or channel.", "/msg <target> <message>", 2, IrcProtocol.Instance);
				yield return new InputCommand (this, "names", "Returns a list of who is on the comma-separated list of <channels>, by channel name. If <channels> is omitted, all users are shown, grouped by channel name with all users who are not on a channel being shown as part of channel \"*\". If <server> is specified, the command is sent to <server> for evaluation.", "/names <channels> [server]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "nick", "Allows a client to change their IRC nickname. Hopcount is for use between servers to specify how far away a nickname is from its home server.", "/nick <nickname>", 1, IrcProtocol.Instance);
				yield return new InputCommand (this, "notice", "This command works similarly to MSG, except automatic replies must never be sent in reply to NOTICE messages.", "/notice <target> <message>", 2, IrcProtocol.Instance);
				yield return new InputCommand (this, "oper" ,"Authenticates a user as an IRC operator on that server/network.", "/oper <username> <password>", 2, IrcProtocol.Instance);
				yield return new InputCommand (this, "part", "Causes a user to leave the channels in the comma-separated list <channels>.", "/part <channels>", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "quit", "Disconnects the user from the server. Optionally you can provide a [reason] to be shown to other clients.", "/quit [reason]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "servlist", "Used to list the services currently on the network.", "/servlist [[mask] [type]]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "squery", "Identical to MSG except the recipient must be a service.", "/squery <service> <message>", 2, IrcProtocol.Instance);
				yield return new InputCommand (this, "stats", "Returns statistics about the current server, or [server] if it's specified.", "/stats <query> [server]", 1, IrcProtocol.Instance);
				yield return new InputCommand (this, "time", "Returns the local time on the current server, or [server] if specified.", "/time [server]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "topic", "Allows the client to query or set the channel topic on <channel>. If you are typing the command from a channel window, you may ommit that parameter. If [topic] is given, it sets the channel topic to [topic]. If channel mode +t is set, only a channel operator may set the topic.", "/topic <channel> [topic]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "userhost", "Returns a list of information about the <nicknames> specified.", "/userhost <usernames>", 1, IrcProtocol.Instance);
				yield return new InputCommand (this, "users", "Returns a list of users and information about those users in a format similar to the UNIX commands who, rusers and finger. If the [server] is ommited, it will return information for the current server.", "/users [server]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "version", "Returns the version of [server], or the current server if omitted.", "/version [server]", 0, IrcProtocol.Instance);
				yield return new InputCommand (this, "wallops", "This command sends <message> to all operators connected to the server (RFC1459), or all users with user mode 'w' set (RFC2812).", "/wallops <message>", 1, IrcProtocol.Instance);
				yield return new InputCommand (this, "who", "This command returns a list of users who match <name>. If the flag [-o] is given, the server will only return information about IRC Operators.", "/who <name> [-o]", 1, IrcProtocol.Instance);
				yield return new InputCommand (this, "whois", "This returns information about the comma-separated list of nicknames masks <nicknames>. If <server> is given, the command is forwarded to it for processing.", "/whois <nicknames> [server]", 1, IrcProtocol.Instance);
				yield return new InputCommand (this, "whowas", "This command is used to return information about a nickname that is no longer in use (due to client disconnection, or nickname changes). If given, the server will return information from the last <count> times the nickname has been used. If <server> is given, the command is forwarded to it for processing. In RFC2812, <nickname> can be a comma-separated list of nicknames.", "/whowas <nicknames> [count [server]]", 1, IrcProtocol.Instance);
				
				// Official IRC commands for Operators, need to be implemented
				
				// Custom commands that we are adding for user friendliness
				//yield return new InputCommand (this, "op", "Give a contact the operator status (only works if you are an operator).", "/op <contact_name>", 1, IrcProtocol.Instance);
				//yield return new InputCommand (this, "deop", "Take away the operator status from a  a contact (only works if you are an operator).", "/deop <contact_name>", 1, IrcProtocol.Instance);
				//yield return new InputCommand (this, "halfop", "Give a contact the halfop status (only works if you are an operator).", "/halfop contact_name", 1, IrcProtocol.Instance);
				//yield return new InputCommand (this, "dehalfop", "Take away the halfop status from a  a contact (only works if you are an operator).", "/dehalfop <contact_name>", 1, IrcProtocol.Instance);
				//yield return new InputCommand (this, "voice", "Give a contact a voice (only works if you are an operator).", "/voice <contact_name>", 1, IrcProtocol.Instance);
				//yield return new InputCommand (this, "devoice", "Take away the voice from a  a contact (only works if you are an operator).", "/devoice <contact_name>", 1, IrcProtocol.Instance);
			}
		}
		
		public bool HandleCommand (object sender, ISession sess, IConversation conv, string command, string parameter, out string output)
		{
			InputCommand inputCommand = null;
			//IrcChatWidget chatWidget = sender as IrcChatWidget;
			IrcSession session = sess as IrcSession;
			IrcConversation conversation = conv as IrcConversation;
			
			output = null;
			
			// Obtain the parameters
			List<string> parameters = new List<string> (parameter.Trim().Split (' '));
			List<string> toRemove = new List<string> ();
			
			// Clear out blank parameters
			foreach (string param in parameters)
				if (String.IsNullOrEmpty(param))
					toRemove.Add (param);
			
			foreach (string param in toRemove)
				parameters.Remove (param);
			
			// Check if the parameters are enough
			foreach (InputCommand cmd in SupportedCommands)
			{
				if (cmd.Command.ToLower().CompareTo (command.ToLower()) == 0)
				{
					if (parameters.Count < cmd.RequiredParams)
					{
						// We dont have enough parameters, show help.
						output = GenerateHelp (cmd);
						return false;
					}
					
					inputCommand = cmd;
				}
			}
			
			switch (inputCommand.Command)
			{
				case "admin":
					if (parameters.Count < 1)
						session.GetAdmin (null);
					else
						session.GetAdmin (parameters[0]);
					return true;
				
				case "away":
					session.ChangeAway (parameter);
					return true;
				
				case "help":
					foreach (InputCommand cmd in SupportedCommands)
					{
						if (cmd.Command.ToLower().CompareTo (parameters[0].ToLower()) == 0)
						{
							
							output = GenerateHelp (cmd);
							return false;
						}
					}
					
					output = "Not a recognized command: "+parameters[0].ToLower();
					return false;
					
				
				case "info":
					if (parameters.Count < 1)
						session.GetInfo (null);
					else
						session.GetInfo (parameters[0]);
					return true;
				
				case "invite":
					session.InviteUser (parameters[1], parameters[0]);
					return true;
				
				case "join":
					string key = parameters.Count == 2 ? parameters[1] : null;
					session.JoinChannel (parameters[0], key, true, false);
					return true;
				
				case "kick":
					if (parameters.Count > 2)
					{
						string message = parameter.Substring (parameter.IndexOf (parameters[1])+parameters[1].Length+1);
						session.KickUser (parameters[0], parameters[1], message);
					}
					else
						session.KickUser (parameters[0], parameters[1]);
					return true;
				
				case "list":
					if (parameters.Count == 1)
						session.ListChannels (parameters[0], null);
					else if (parameters.Count == 2)
						session.ListChannels (parameters[0], parameters[1]);
					else
						session.ListChannels (null, null);
					return true;
				
				case "lusers":
					if (parameters.Count > 1)
						session.GetLUsers(parameters[0], parameters[1]);
					else if (parameters.Count > 0)
						session.GetLUsers(parameters[0], null);
					else
						session.GetLUsers (null, null);
					return true;
				
				case "mode":
					if (parameters.Count > 2)
						session.ChangeMode (parameters[0], parameters[1], parameters[2]);
					else
						session.ChangeMode (parameters[0], parameters[1], null);
					return true;
				
				case "motd":
					if (parameters.Count > 0)
						session.GetMOTD (parameters[0]);
					else
						session.GetMOTD (null);
					return true;
				
				case "msg":
					session.SendPrivateMessage (parameters[0], parameters[1]);
					return true;
				
				case "names":
					if (parameters.Count == 0)
						session.GetNames (null, null);
					else
						session.GetNames (parameters[0], parameters.Count > 1 ? parameters[1] : null);
					return true;
				
				case "nick":
					session.ChangeNickname (parameters[0]);
					return true;
				
				case "notice":
					session.SendPrivateNotice (parameters[0], parameters[1]);
					return true;
				
				case "oper":
					session.AuthOperator(parameters[0], parameters[1]);
					return true;
				
				case "part":
					if (parameters.Count == 0)
					{
						if (conversation != null)
						{
							if (!conversation.IsPrivateConversation)
								session.PartChannels (conversation.PrimaryContact.DisplayName);
						}
						else
						{
							output = GenerateHelp (inputCommand);
							return false;
						}
					}
					else
						session.PartChannels (parameters.ToArray());
					return true;
				
				case "quit":
					session.Quit (parameters.Count < 1 ? null: parameter);
					return true;
				
				case "servlist":
					if (parameters.Count > 1)
						session.GetServList (parameters[0], parameters[1]);
					else if (parameters.Count > 0)
						session.GetServList (parameters[0], null);
					else
						session.GetServList (null, null);
					return true;
				
				case "squery":
					session.GetServQuery (parameters[0], parameters[1]);
					return true;
				
				case "stats":
					if (parameters.Count > 1)
						session.GetStats (parameters[0], parameters[1]);
					else
						session.GetStats (parameters[0], null);
					return true;
				
				case "time":
					if (parameters.Count > 0)
						session.GetTime (parameters[0]);
					else
						session.GetTime (null);
					return true;
				
				case "topic":
					if (parameters.Count >= 1)
					{
						// We have provided at least 1 parameter, it may be a channel or a word.
						if (parameters[0][0] == '#')
						{
							// we have provided a channel as the first parameter
							session.ChangeTopic (parameters[0], parameter.Substring (parameters[0].Length+1));
						}
						else if (conversation != null)
						{
							// We have not provided a channel, but we are calling this from one.
							session.ChangeTopic (conversation.PrimaryContact.DisplayName, parameter);
						}
						else
						{
							// we have not provided a channel, and we are not in one!
							output = GenerateHelp (inputCommand);
							return false;
						}
					}
					else
					{
						if (conversation == null)
						{
							output = GenerateHelp (inputCommand);
							return false;
						}
						else
							session.ChangeTopic (conversation.PrimaryContact.DisplayName, String.Empty);
					}
					return true;
				
				case "userhost":
					session.GetUserHost (parameter);
					return true;
				
				case "users":
					session.GetUsers (parameters.Count > 0 ? parameters[0] : null);
					return true;
				
				case "version":
					session.GetVersion (parameters.Count > 0 ? parameters[0] : null);
					return true;
				
				case "wallops":
					session.SendWallOps (parameter);
					return true;
				
				case "who":
					session.WhoUser (parameters[0]);
					return true;
				
				case "whois":
					session.WhoisUser (parameters[0]);
					return true;
				
				case "whowas":
					session.WhowasUser (parameters[0]);
					return true;
				
				/*
				case "op":
					break;
					
				case "deop":
					break;
					
				case "halfop":
					break;
					
				case "dehalfop":
					break;
					
				case "voice":
					break;
					
				case "devoice":
					break;*/
			}
			
			output = "Command has not been implemented yet: "+command;
			return false;
		}
		
		private string GenerateHelp (InputCommand command)
		{
			string error = command.Command.ToUpper() + ":\n"+command.Description + "\n\n";
			error += "Usage: "+command.Sample + "\n";
			
			return error;
		}
	}
}